package com.inepex.inechart.client.axes;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;

import com.inepex.inechart.client.Defaults;
import com.inepex.inechart.client.axes.IncrementalAxisDrawer.DrawingAreaProviderForAxis;
import com.inepex.inechart.client.jobscheduler.HasScheduledJob;
import com.inepex.inechart.client.label.GWTLabelFactory;
import com.inepex.inechart.shared.CartesianException;
import com.inepex.inechart.shared.axes.Axes;
import com.inepex.inechart.shared.axes.Axis;
import com.inepex.inechart.shared.axes.Tick;

public class AxesGWT implements Axes {

	protected TickFactoryGWT tickFactory;
	protected GWTLabelFactory labelFactory;
	protected TreeMap<Axis, AxisPresenterAssist> axisPresenterMap;
	protected TreeMap<Axis, double[]> paddingNeededForAxis;
	protected TreeMap<Axis, ArrayList<double[]>> ticksCreatedIntervals;

	public AxesGWT(TickFactoryGWT tickFactory, GWTLabelFactory labelFactory) {
		this.labelFactory = labelFactory;
		this.tickFactory = tickFactory;
		axisPresenterMap = new TreeMap<Axis, AxisPresenterAssist>();
		paddingNeededForAxis = new TreeMap<Axis, double[]>();
		ticksCreatedIntervals = new TreeMap<Axis, ArrayList<double[]>>();
	}

	@Override
	public void addAxis(Axis axis) {
		axisPresenterMap.put(axis, new AxisPresenterAssist(axis));
	}

	@Override
	public void removeAxis(Axis axis) {
		axisPresenterMap.remove(axis);
	}
	
	@Override
	public double[] getActualModulPaddingForAxis(Axis axis) {
		return axisPresenterMap.get(axis).getMergedTickPaddings();
	}

	/*tick creation and padding calculation */

	/**
	 * Call this method when the resolution of the axis is changed after last update,
	 * so the auto created ticks will be cleared.
	 * @param axis
	 */
	public void axisResolutionChanged(Axis axis){
		if(axis.isAutoCreateTicks()){
			axis.getTicks().clear();
			tickFactory.clearAutoCreationParameters(axis);
		}
	}
	
	/**
	 * (Auto)Creates and/or customizes ticks (depending on related options)
	 * NOTE that this method creates ticks IF AND ONLY IF the {@link Axis#isAutoCreateTicks()} is true!
	 * (So it will not check if any ticks are present in the given interval) 
	 * @param axis target axis
	 * @param from inclusive
	 * @param to inclusive
	 */
	public void createAndCustomizeTicksIf(Axis axis, double from, double to){
		if (axis.isAutoCreateTicks()){
			// auto calc tick's positions
			axis.getTicks().addAll(tickFactory.autoCreateTicks(axis, from, to));
		}
		Collections.sort(axis.getTicks());
		for (Tick t : axis.getTicks()) {
			if(t.getPosition() < from) continue;
			if(t.getPosition() > to) break;
			customizeTick(t, axis);
		}
	}

	/**
	 * Customizes the given tick by the related parameters of its axis
	 * @param tick
	 * @param axis the axis of the given tick
	 */
	private void customizeTick(Tick tick, Axis axis){
		if(tick.getFormatString() != null && (axis.getTickCustomizer() == null || tick.getText() == null || tick.getText().getText() == null)){
			tick.getText().setText( tickFactory.formatTickText(tick, axis.getAxisDataType()));
		}
		Tick defaultTick = axis.getDefaultTick();
		//use given defaults
		if(defaultTick != null) {
			if(defaultTick.getFormatString() != null){
				tick.setFormatString(defaultTick.getFormatString());
			}
			tick.setTextContainer(defaultTick.getTextContainer());
			tick.getText().setTextProperties(defaultTick.getText().getTextProperties());
			tick.setGridLine(defaultTick.getGridLine());
			tick.setTickLine(defaultTick.getTickLine());
			tick.setTickPosition(defaultTick.getTickPosition());
			tick.setTickLength(defaultTick.getTickLength());				
		}
		// use default defaults :)
		else{
			if(axis.isAutoCreateGrids()	){
				tick.setGridLine(Defaults.gridLine());
			}
			tick.setText(new com.inepex.inechart.shared.label.Text(tick.getPosition() + ""));
		}

		if(axis.getTickCustomizer() != null){
			axis.getTickCustomizer().setTick(tick, axis);
		}
	}
	
	/**
	 * Call this method to calculate scheduled the padding needed for an axis
	 * @param axes
	 * @param jobScheduler
	 * @param updatePartTreshold
	 * @param xFrom
	 * @param xTo
	 * @param yFrom
	 * @param yTo
	 * @throws CartesianException
	 */
	public void calcPaddingForAxes(List<Axis> axes, final HasScheduledJob jobScheduler, int updatePartTreshold, double xFrom,
			double xTo, double yFrom, double yTo) throws CartesianException{
		new IncrementalAxisCalculator(updatePartTreshold, axes, this, true, xFrom, xTo, yFrom, yTo) {
			
			@Override
			protected void onFinish() {
				jobScheduler.onJobFinish();
			}
		};
			
	}

	public void createGOsForAxes(List<Axis> axes, final HasScheduledJob jobScheduler, int updatePartTreshold, double xFrom,
			double xTo, double yFrom, double yTo) throws CartesianException{
		new IncrementalAxisCalculator(updatePartTreshold, axes, this, false, xFrom, xTo, yFrom, yTo) {
			
			@Override
			protected void onFinish() {
				jobScheduler.onJobFinish();
			}
		};			
	}
	
	public AxisCalculatorGWT getAxisCalculatorGWTForAxis(Axis axis){
		return axisToAxisCalcMap.get(axis);
	}

	public void drawAxes(List<Axis> axesToDraw, final HasScheduledJob jobScheduler, int updatePartTreshold, DrawingAreaProviderForAxis drawingAreaProviderForAxis){
		new IncrementalAxisDrawer(this, axesToDraw, updatePartTreshold, drawingAreaProviderForAxis) {
			
			@Override
			public void onFinish() {
				jobScheduler.onJobFinish();
			}
		};
	}

}
